<?php
namespace PI;



class ModelNamespace{
    public $dir;
    public $name;   
    public $fullname;
    public $types;
    public $super;
    public $subs;
    public function __construct(string $name,ModelNamespace $super=null){
        $this->super = $super;
        
        if(!$super){
            $this->name = "";
            $this->dir = $name;
        }else {
            $this->name = $name;
            $this->dir = "$super->dir/$name";
        }
        $this->fullname = $this->super?$this->super->fullname.".$name":"";
        
    }
    
    function get_rootNamespace(){
        $p = $this;
        while($p){
            if(!$p->super)return $p;
            $p = $p->super;
        }
    }

    function load():ModelNamespace{
        if($this->types!==null) return $this;
        if(!is_dir($this->dir)) throw new \Exception("错误的路径,$this->dir");
        

        $dh = opendir($this->dir);
        if(!$dh) throw new \Exception("无法打开文件目录 $this->dir");
        $this->subs = [];
        $this->types = [
            "int"=>new ModelType(null,"int",null)
            , "float"=>new ModelType(null,"float",null)
            , "doublie"=>new ModelType(null,"double",null)
            , "decimal"=>new ModelType(null,"decimal",null)
            , "uint"=>new ModelType(null,"uint",null)
            , "string"=>new ModelType(null,"string",null)
            , "bool"=>new ModelType(null,"bool",null)
            , "date"=>new ModelType(null,"date",null)
            , "datetime"=>new ModelType(null,"datetime",null)
        ];

        while (($file = readdir($dh)) !== false) {
            if($file==="." || $file==="..")continue;
            $fullname = $this->dir."/".$file;
            $ftype = filetype($fullname);
            if($ftype==="dir") $this->subs["$file"] = new ModelNamespace($file,$this);
            else {
                if(preg_match('/.model.json$/i',$file)){
                    $shortName = substr($file,0,strlen($file)-11);
                    $jsonText = file_get_contents($fullname);
                    $json = json_decode($jsonText,true);
                    $this->types[$shortName] = new ModelType($this,$shortName,$json);
                }
            }
        }
        return $this;
    }

    function get_types(){
        if($this->types===null) $this->load();
        return $this->types;
    }
    function get_subs(){
        if($this->subs===null) $this->load();
        return $this->subs;
    }

    function get_sub($name){
        if($this->subs===null) $this->load();
        if(isset($this->subs[$name])) return $this->subs[$name];
        $dir = "$this->dir/$name";
        if(is_dir($dir)) return $this->subs[$name] = new ModelNamespace($name,$this);
        else return $this->subs[$name]= null;
    }
    function get_type(string $name,bool $sure=false):ModelType{
        if($this->subs===null) $this->load();
        if(isset($this->types[$name])) return $this->types[$name];
        $names = explode(".",$name);
        //没有名字空间
        if(count($names)===0){
            $filename = "$this->dir/$name.model.json";
            if(file_exists($filename)){
                $jsonText= file_get_contents($filename);
                $json = json_decode($jsonText,true);
                $modelDef = new ModelType($this,$name,$json);
                $this->defs[$name] =  $this->defs["$this->fullname.$name"] = $modelDef;
                
            }else {
                if($this->super) $this->super->get_type($name,true);
                else return null;
            }
        }else {
            $ns= $this->get_rootNamespace();
            $name1 = array_pop($names);
            while($nsName = array_shift($names)){
                if(!$ns= $ns->get_sub($nsName)) throw new \Exception("无法找到$name 所在的目录");
                else return null;
            }
            return $ns->get_type($name1,false);
        }
    }

    function make(string $targetDir=null){

    }
}

//模型类型信息
class ModelType{
    //原始的json定义
    public $defination;
    //名字空间
    public $namespace;
    //模型类型的全名，为了兼容其他的c like 程序，格式为 ns1.ns2.type
    public $fullname;
    //模型的短名称
    public $name;
    
    //模型的基类列表
    public $extends;
    //模型的属性集合，不应该公开使用
    private $_props;
    //模型的主键属性
    public $primaryProp;

    function __construct(ModelNamespace $ns=null,string $name,$defination=null){
        $this->namespace = $ns;
        $this->defination = $defination;
        $this->name = $name;
        if($ns){
            $this->fullname = $ns->fullname?"$ns->fullname.$name":$name;
            $this->init_extends();
        }
        
        
    }
    //初始化基类
    function init_extends(){
        $this->_extends = [];
        if(!isset($this->defination["extends"])) return $this;
        if(!($extends = $this->defination["extends"])) return $this;
        
        for($i=0,$j=count($extends);$i<$j;$i++){
            $basClsname = $extends[$i];
            $basType =  $this->ns->get_type($basClsname);
            $this->check_extends($basType);
            $this->_extends[] = $basType;
        }
        return $this->_extends;
    }
    //基类是否在循环
    private function check_extends(ModelType $basType){
        if($basType===$this){
            throw new \Exception("$this->fullname 的基类列表中不能有自己");
        }
        if($basType->extends){
            foreach($basType->extends as $basBas){
                $this->check_extends($basBas);
            }
        }
        
    }

    function get_props(){
        if($this->_props!==null) return $this->_props;
        $this->props= [];
        if(!$this->defination) return $this->props;
        $propDefs = isset($this->defination["props"])?$this->defination["props"]:null;
        if($propDefs){
            foreach($propDefs as $k=>$v){
                $prop = new ModelProperty($this,$k,$v);
                if($prop->primary) $this->primaryProp = $prop;
                $this->props[] = $prop;
                if($prop->plantable){
                    $propType = $prop->propertyType;
                    $subProps = $propType->get_props();
                    foreach($subProps as $subProp){
                        if($subProp->important){
                            $insertedProp = $subProp->clone();
                            $insertedProp->ownType = $this;
                            $insertedProp->name = $prop->name. $subProp->name;
                            $this->replace_OrAddProp(function($item,$newItem){
                                if ($item->name === $newItem->name) {
                                    return $item;
                                }
                            },$insertedProp);
                        }
                    }
                }
            }
        }
        $callback = function($prop,$basProp){
            if($prop->name === $basProp->name){
                if(!$prop->data) return $basProp;
                else return $prop;
            }
        };
        for($i=0,$j=count($this->extends);$i<$j;$i++){
            $superType = $this->extends[$i];
            $superProps = $superType->get_props();
            if($i===0){
                foreach($superProps as $superProp){
                    if(!$this->primaryProp && $superProp->primary) $this->primaryProp= $superProp;
                    $this->replace_OrAddProp($callback,$superProp,false);
                }
            }else {
                foreach($superProps as $superProp){
                    $this->replace_OrAddProp($callback,$superProp,true);
                }
            }
        }
    }
    
    function get_prop($name):ModelProperty{
        $props = $this->get_props();
        foreach($props as $prop) {
            if($prop->name===$name) return $prop;
        }
        return null;
    }

    private function replace_OrAddProp($callback,$newProp,$usePush=true){
        $replaced = false;
        for($i=0,$j=count($this->props);$i<$j;$i++){
            $prop = array_shift($this->props);
            $rs = $callback($prop,$newProp);
           
            if($rs instanceof ModelProperty) {
                array_push($this->props,$rs);
                $replaced = true;
            }
            else array_push($prop); 
        }
        if($replaced){
            if($usePush) array_push($this->props,$newProp);
            else array_unshift($this->props,$newProp);
        }
        return $this;
    }
    
}


class ModelProperty{
    public $name;
    public $required;
    public $type;
   
    public $plantable;
    public $isArray;
    public $itemType;
    //引用的类型的shortName
    public $refTypename;
    public $propertyType;

    public $length;
    public $rules;
    public $reference;
    public $data;
    public $ownType;
    public $primary;
    public $index;
    function __construct(ModelType $modelType=null,string $name=null,array $data=null){
        if($modelType===null)return;
        $this->data = $data;
        $this->ownType = $modelType;
        $this->name = $name;
        if($data && isset($data["type"])) $this->type = $data["type"];
        else $this->type = "string";
        if($this->type==="text") $this->type = "string";

        $this->isArray=false;
        $shortName = $this->type;
        $at = strlen($this->type)-2;
        if($at>0){
            if(substr($this->type,$at,2)==="[]") {
                $this->isArray=true;
                    $shortName =  $this->itemType = substr($this->type,$at);
            }
        }
        $at = strrpos($shortName,".");
        if($at!==false){
            $shortName = substr($shortName,$at+1);
        }
        $this->refTypename = $shortName;
        $this->propertyType = $this->ownType->ns->get_type($this->isArray?$this->type:$this->itemType);
        
        if(!$data){
            return;
        }
        

        if(isset($data["primary"])) $this->primary = $data["primary"];

        if(isset($data["index"])) $this->index = $data["index"];

        if(isset($data["required"])) $this->required = $data["required"];

        if(isset($data["plantable"])) $this->required = $data["plantable"];

        if(isset($data["rules"])) $this->rules = $data["rules"];
        else $this->rules=[];

        if(isset($data["length"])) $this->length = $data["length"];
        $length = isset($this->rules["length"])?$this->rules["length"]:[];
        if(!is_array($length)) $length = [0,$length];
        if(!is_array($this->length) && $length){
            if($this->length<$length[1]) $length[1] = $this->length;
        }else {
            if($this->length[0]>$length[0]) $length[0] = $this->length[0];
            if($this->length[1]<$length[1]) $length[1] = $this->length[1];
        }
        if($length) {
            $this->rules["length"] = $length;
            $this->length = $length[1];
        }
        
        if(isset($this->data["hasOne"])){
            if($this->isArray) throw new \Exception("字段$this->name的type与hasOne冲突,在$this->modelDef->fullname");
            $ref = new ModelReference(false,$this->name,$this->refTypename);
            $foreignKey = $this->data["hasOne"];
            if(is_string($foreignKey)) $ref->foreignKey=$foreignKey;
            else $ref->foreignKey = $this->modelDef->name."Id";
        }if(isset($this->data["ownOne"])){
            if($this->isArray) throw new \Exception("字段$this->name的type与ownOne冲突,在$this->modelDef->fullname");
            $ref = new ModelReference(false,$this->name,$this->refTypename);
            $primaryKey = $this->data["ownOne"];
            if(is_string($primaryKey)) $ref->primaryKey=$primaryKey;
            else $ref->primaryKey = $this->type."Id";
        }if(isset($this->data["hasMany"])){
            if(!$this->isArray) throw new \Exception("字段$this->name的type与hasMany冲突,在$this->modelDef->fullname");
            $ref = new ModelReference(true,$this->name,$this->refTypename);
            $foreignKey = $this->data["hasMany"];
            if(is_string($foreignKey)) $ref->foreignKey=$foreignKey;
            else $ref->foreignKey = $this->modelDef->name."Id";
        }if(isset($this->data["manyMany"])){
            if($this->isArray) throw new \Exception("字段$this->name的type与manyMany冲突,在$this->modelDef->fullname");
            $ref = new ModelReference(true,$this->name,$this->refTypename);
            $link = $this->data["manyMany"];
            if(is_string($link)) {
                $ref->linkType = $link;
                $ref->leftKey = $this->modelDef->name."Id";
                $ref->rightKey = $this->refTypename."Id";
            }else {
                $ref->linkType = $link[0];
                $ref->leftKey = $link[1];
                $ref->rightKey = $link[2];
            }
        }
        $this->reference = $ref;

    }

    function clone(){
        $target = new ModelProperty();
        foreach($this as $k=>$v) $target->$k=$v;
        return $target;

    }
}

class ModelReference{
    public $name;
    public $isMany;
    public $primaryKey;
    public $foreignKey;
    public $foreignType;
    public $linkType;
    public $leftKey;
    public $rightKey;
    function __construct($isMany,$name,$foreignType){
        $this->isMany = $isMany;
        $this->name = $name;
        $this->foreignType = $foreignType;
    }

}

//名字空间的生成器
class NamespaceMaker{
    public $namespace;
    public $targetDir;
    function __construct(ModelNamespace $ns,$targetDir){
        $this->namespace = $ns;
        $this->targetDir = $targetDir;
    }
}
class CodeGen{

    
}

class ListCodeGen{
    public $type;
    public $pageSetting;
    public $queryViewCode;
    function __construct(ModelType $type,$pageSetting){
        $this->type = $type;
        $this->pageSetting= $pageSetting;
    }
    function generate(){        
        $props = $this->type->get_props();
        $cols = $this->pageSetting["cols"];

        $queryModelCode = "";
        $listTHeadViewCode = "";
        $listTBodyViewCode = "";
        $queryKeyFieldsCode = "";
        $queryExtpandFieldCode = "";
        $queryValidateCode = "";
        $whereBuilderCode = "";
        $viewModelTypename = $this->type->fullname."ViewModel";
        //col有哪些字段?
        foreach($cols as $name =>$col){
            $prop = $this->type->get_prop($name);
            if(!$prop) continue;
            $isQueryKeyField=false;
            if(!is_array($col)){
                $queryable = $prop->queryable;
                $listable = $prop->listable;
            }else {
                if(isset($col["keyQuery"])){
                    $queryable = isset($col["keyQuery"])?$col["keyQuery"]:null;
                    $isQueryKeyField = true;

                }else  if(isset($col["queryable"])){
                    $queryable = isset($col["queryable"])?$col["queryable"]:null;
                }
                
                $listable = isset($col["listable"])?$col["listable"]:false;
            }
            $label = $prop->label;
            if(!$label) $label = $prop->name;
            if($queryable){
                $whereBuilderCode.="\t\tif(\$currPermissions->check('$prop->name')) \$whereBuilder->$queryable();\n";
                $queryFieldCode="\t\t\t<."."?php $this->render_formField(\$states,'$prop->name','$label','$prop->name',$viewModelTypename"."::$prop->name"."Info,false); ?".">\n";
                
                if(!is_string($queryable)){
                    if($prop->type==="string" || $prop->type==="text"){
                        $queryable="contains";
                    }else if($prop->type==="datetime"||$prop->type==="date"){
                        $queryable="range";
                    }else $queryable= "eq";
                }
                if($isQueryKeyField) $queryKeyFieldsCode.= $queryFieldCode;
                else $queryExtpandFieldCode.= $queryExtpandFieldCode;

                if($queryable==="range"){
                    $queryModelCode.="\tpublic $prop->name"."_min;";
                    $queryModelCode.="\tpublic $prop->name"."_max;";

                    $queryValidateCode.="\t\t\$vaild = $viewModelTypename"."::validate_$prop->name"."(\$vm->$prop->name"."_min,false);\n\t\tif(\$valid!==true) \$result['\$vm->$prop->name"."_min']=\$valid;\n";
                    $queryValidateCode.="\t\t\$vaild = $viewModelTypename"."::validate_$prop->name"."(\$vm->$prop->name"."_max,false);\n\t\tif(\$valid!==true) \$result['\$vm->$prop->name"."_max']=\$valid;\n";
                }else{
                    $queryModelCode.="\tpublic $prop->name;";
                    $queryValidateCode.="\t\t\$vaild = $viewModelTypename"."::validate_$prop->name"."(\$vm->$prop->name,false);\n\t\tif(\$valid!==true) \$result['\$vm->$prop->name']=\$valid;\n";
                   
                   
                }
            }

            if($listable!==null && $listable!==false){
                $isHidden=false;
                $url = null;
                #cellBuilderName = null;
                if(is_array($listable)){
                    $isHidden = isset($listable['hidden'])?$listable['hidden']:false;
                    $url = isset($listable['url'])?$listable['url']:null;
                    $cellBuilder= isset($listable['cell'])?$listable['cell']:null;
                }else {
                    if($listable==="#hidden")$isHidden=true;
                    if(is_string($listable)){
                        $cmd =substr($listable,0,1);
                        if($cmd==="@")$url = substr($listable,1);
                        if($cmd==="!")$cellBuilder = substr($listable,1);
                    }  
                }
                
                
                if($isHidden){
                    $listTHeadViewCode .="\t\t\t<th style='display:none;'>$label</th>\n";
                    $listTBodyViewCode .="\t\t\t<td style='display:none;'><"."?=\$row['$prop->name'] ?"."></td>\n";
                }else {
                    $listTHeadViewCode .="
            <th class='$prop->type $prop->name <"."?=\$states->order_asc==='$prop->name'?'asc':(\$states->order_desc==='$prop->name'?'desc':'') ?".">' >
                <a href='<"."? echo \$this->makeOrderUrl('$prop->name',\$states->order_asc==='$prop->name'?'asc':(\$states->order_desc==='$prop->name'?'desc':'')) ?".">'>$label</a>
            </th>";
                    if($cellBuilder){
                        $listTBodyViewCode .="
            <td class='$prop->type $prop->name' ><"."?php \$this->build_cell('$cellBuilder',\$row) ?"."></td>\n";
                    }else if($url){
                        $listTBodyViewCode .="
            <td class='$prop->type $prop->name'><a href='$url'>\$row['$prop->name']</a></td>\n";
                    }else {
                        $listTBodyViewCode .="
            <td class='$prop->type $prop->name'>\$row['$prop->name']</td>\n";
                    }
                    
                }
            }

        }

        $queryViewCode.="";
        is_array($this->pageSetting)?$this->pageSetting:$props;


    }

}

